3.4.4 函数式编程

介绍:

  • 与面向对象编程(Object-oriented programming)和过程式编程(Procedural programming)并列的编程范式。
  • 最主要的特征是,函数是第一等公民。
  • 强调将计算过程分解成可复用的函数,典型例子就是map方法和reduce方法组合而成 MapReduce 算法。
  • 只有纯的、没有副作用的函数,才是合格的函数。

函数式编程有两个最基本的运算:合成和柯里化。

函数的合成与柯里化

  • 要经过多个函数,才能变成另外一个值,就可以把所有中间步骤合并成一个函数,这叫做"函数的合成"(compose)。
  • 所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。

函子

是函数式编程里面最重要的数据类型,也是基本的运算单位和功能单位

函子的代码实现

任何具有map方法的数据结构,都可以当作函子的实现。

class Functor {
  constructor(val) { 
    this.val = val; 
  }

  map(f) {
    return new Functor(f(this.val));
  }
}
1
2
3
4
5
6
7
8
9

函子的标志就是容器具有map方法。该方法将容器里面的每一个值,映射到另一个容器

学习函数式编程,实际上就是学习函子的各种运算

of

函数式编程一般约定,函子有一个of方法,用来生成新的容器。

下面就用of方法替换掉new。

Functor.of = function(val) {
  return new Functor(val);
};
1
2
3

Maybe 函子

函子接受各种函数,处理容器内部的值。这里就有一个问题,容器内部的值可能是一个空值(比如null),
而外部函数未必有处理空值的机制,如果传入空值,很可能就会出错。

Functor.of(null).map(function (s) {
  return s.toUpperCase();
});
// TypeError
1
2
3
4

上面代码中,函子里面的值是null,结果小写变成大写的时候就出错了。

Maybe 函子就是为了解决这一类问题而设计的。简单说,它的map方法里面设置了空值检查。

class Maybe extends Functor {
  map(f) {
    return this.val ? Maybe.of(f(this.val)) : Maybe.of(null);
  }
}
1
2
3
4
5

Either 函子

条件运算if...else是最常见的运算之一,函数式编程里面,使用 Either 函子表达。

Either 函子内部有两个值:左值(Left)和右值(Right)。右值是正常情况下使用的值,左值是右值不存在时使用的默认值。


var addOne = function (x) {
  return x + 1;
};

Either.of(5, 6).map(addOne);
// Either(5, 7);

Either.of(1, null).map(addOne);
// Either(2, null);
1
2
3
4
5
6
7
8
9
10

ap 函子

函子里面包含的值,完全可能是函数。我们可以想象这样一种情况,一个函子的值是数值,另一个函子的值是函数。

ap 是 applicative(应用)的缩写。凡是部署了ap方法的函子,就是 ap 函子。

class Ap extends Functor {
  ap(F) {
    return Ap.of(this.val(F.val));
  }
}
1
2
3
4
5

注意,ap方法的参数不是函数,而是另一个函子。

Monad 函子

Maybe.of(
  Maybe.of(
    Maybe.of({name: 'Mulburry', number: 8402})
  )
)
1
2
3
4
5

上面这个函子,一共有三个Maybe嵌套。如果要取出内部的值,就要连续取三次this.val。这当然很不方便,因此就出现了 Monad 函子。

Monad 函子的作用是,总是返回一个单层的函子。它有一个flatMap方法,与map方法作用相同,唯一的区别是如果生成了一个嵌套函子,
它会取出后者内部的值,保证返回的永远是一个单层的容器,不会出现嵌套的情况。

参考